home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Source.bin / ListSpinner.java < prev    next >
Text File  |  1998-08-21  |  19KB  |  630 lines

  1. package symantec.itools.awt.util.spinner;
  2.  
  3.  
  4. import java.awt.Toolkit;
  5. import java.util.Enumeration;
  6. import java.util.Vector;
  7. import java.beans.PropertyVetoException;
  8. import java.beans.PropertyChangeListener;
  9. import java.beans.VetoableChangeListener;
  10. import java.awt.event.FocusEvent;
  11. import java.awt.FontMetrics;
  12.  
  13.  
  14. //    06/03/97    LAB    Updated to support Java 1.1.
  15. //                    Changed the package to symantec.itools.awt.util.spinner
  16. //  08/24/97    CAR overrode updateText to handle changes to the text in textField
  17. //                  added calls to invalidate/validate in addItem so that textField
  18. //                  will be properly sized after text items are added
  19. //  08/28/97    LAB    Added allowDynamicResizing property.  Made setMax and setMin work correctly.
  20. //                    Changed the property name strings in bound and constrained event messages to
  21. //                    match the Bean Spec naming conventions.
  22. //  08/29/97    CAR removed calls to setMax from addNotify and addItem as this negates the
  23. //                  value set via the prop list or a preceding call to setMax.  this fixes a bug
  24. //                  re: setting max at design time does not effect runtime max
  25. //  10/01/97    LAB    Updated version to 1.1.  Changed internal workings to solve problems caused by
  26. //                    allowing users to specify a max value that does not get changed by the component
  27. //                    dynamically when items are added (Addresses Mac Bug #8086).  Set max to 10 by
  28. //                    default.
  29. //  10/05/97    LAB    Changed setListItems to erase the vector before adding items so setting
  30. //                    the list to a subset of the original list works properly (Addresses Mac
  31. //                    Bug #8942).  setListItems now updates the state of the component correctly.
  32. //                    Fixed updateText to set the edit box to an empty string if the internal
  33. //                    vector is empty.
  34. //  10/06/97    LAB    Fixed several issues with max and min.  (fixed Mac Bug #8086 again...)
  35. //                    Changed setMax to set the temp value to the current value when changing
  36. //                    the current value.  This fixes a problem at design time where the
  37. //                    component would revert to it's default state after a back run.
  38. //  10/12/97    LAB    Changed addItem to set the size of the component to the preferred size if
  39. //                    dynamic resizing is on and if a change in size is needed.  Changed
  40. //                    updateText to update spinner's internal status correctly. These changes
  41. //                    address Mac bug #9200. Changed getCurrentText to return an empty string
  42. //                    rather than null if there is no current text.  Now if list items are too
  43. //                    long for the size of the textfield, they are truncated with a '...'
  44. //  02/05/98    DS  Re-write of the GUI to allow for resizing
  45.  
  46. /**
  47.  * Creates a text box, containing a list of items, with up and down arrows.
  48.  * Use this component to allow your users to move through a set of fixed
  49.  * values or type a valid value in the box.
  50.  * <p>
  51.  * At run time only the selected value is displayed in the text box.
  52.  * <p>
  53.  * @see symantec.itools.awt.Spinner
  54.  * @version 1.1, October 1, 1997
  55.  * @author Symantec
  56.  */
  57. public class ListSpinner
  58.     extends Spinner
  59. {
  60.     /**
  61.      * Constructs an empty ListSpinner.
  62.      */
  63.     public ListSpinner()
  64.     {
  65.         list = new Vector();
  66.         try
  67.         {
  68.             setAllowDynamicResizing(false);
  69.         }
  70.         catch(PropertyVetoException exc){}
  71.         max        = 10;
  72.         updateInternalMax();
  73.         updateButtonStatus();
  74.     }
  75.  
  76.     /**
  77.      * Adds the given string array to the end of the list.
  78.      * @param items the items to add to the list
  79.      * @see #getListItems
  80.      *
  81.      * @exception PropertyVetoException
  82.      * if the specified property value is unacceptable
  83.      */
  84.     public void setListItems(String[] items) throws PropertyVetoException
  85.     {
  86.         String[] oldValue = getListItems();
  87.  
  88.         if(!symantec.itools.util.GeneralUtils.objectsEqual(oldValue, items))
  89.         {
  90.                vetos.fireVetoableChange("listItems", oldValue, items);
  91.  
  92.             list = new Vector();
  93.             for (int i = 0; i < items.length; ++i)
  94.             {
  95.                 addItem(items[i]);
  96.             }
  97.  
  98.             updateInternalMax();
  99.             if(!isValidCurrentValue(getCurrent()))
  100.                 setCurrent(internalMax);
  101.             updateButtonStatus();
  102.             updateText(false);
  103.  
  104.             changes.firePropertyChange("listItems", oldValue, items);
  105.         }
  106.     }
  107.  
  108.     /**
  109.      * Returns the current list as an array of Strings.
  110.      * @return the current list
  111.      * @see #setListItems
  112.      */
  113.     public String[] getListItems()
  114.     {
  115.         int len = list.size();
  116.         String[] items = new String[len];
  117.         for (int i = 0; i < len; ++i)
  118.         {
  119.             items[i] = (String)list.elementAt(i);
  120.         }
  121.         return items;
  122.     }
  123.  
  124.     /**
  125.      * Conditionally allows resizing of the edit box if needed when new values are entered.
  126.      * @param f if true the edit box will resize if needed when new values are entered.
  127.      * @see #isAllowDynamicResizing
  128.      *
  129.      * @exception PropertyVetoException
  130.      * if the specified property value is unacceptable
  131.      */
  132.     public void setAllowDynamicResizing(boolean f) throws PropertyVetoException
  133.     {
  134.         if(dynamicResizing != f)
  135.         {
  136.             Boolean oldValue = new Boolean( dynamicResizing );
  137.             Boolean newValue = new Boolean( f );
  138.  
  139.             vetos.fireVetoableChange("allowDynamicResizing", oldValue, newValue);
  140.  
  141.             dynamicResizing = f;
  142.  
  143.             changes.firePropertyChange("allowDynamicResizing", oldValue, newValue);
  144.         }
  145.     }
  146.  
  147.     /**
  148.      * Gets whether the current value can wrap around from maximum
  149.      * to minimum and from minimum to maximum.
  150.      * @return true if the edit box will resize if needed when new values are entered.
  151.      * @see #setAllowDynamicResizing
  152.      */
  153.     public boolean isAllowDynamicResizing()
  154.     {
  155.         return dynamicResizing;
  156.     }
  157.  
  158.     /**
  159.      * Gets the currently selected string from the list.
  160.      * @return the string currently visible in the Spinner
  161.      */
  162.     public String getCurrentText()
  163.     {
  164.         return list.size() > 0 ? (String)list.elementAt(current) : "";
  165.     }
  166.  
  167.     /**
  168.      * Sets the maximum value the spinner may have.
  169.      * @param i the new maximum value
  170.      * @see symantec.itools.awt.util.spinner.Spinner#getMax
  171.      *
  172.      * @exception PropertyVetoException
  173.      * if the specified property value is unacceptable
  174.      */
  175.     public void setMax(int i) throws PropertyVetoException
  176.     {
  177.         if(max != i)
  178.         {
  179.             Integer oldValue = new Integer( max );
  180.             Integer newValue = new Integer( i );
  181.  
  182.             vetos.fireVetoableChange("max", oldValue, newValue);
  183.  
  184.             max = i;
  185.  
  186.             if(getCurrent() > max)
  187.                 setCurrent(max);
  188.             else
  189.             {
  190.                 updateInternalMax();
  191.                 updateButtonStatus();
  192.             }
  193.  
  194.             changes.firePropertyChange("max", oldValue, newValue);
  195.         }
  196.     }
  197.  
  198.     /**
  199.      * Tells this component that it has been added to a container.
  200.      * This is a standard Java AWT method which gets called by the AWT when
  201.      * this component is added to a container. Typically, it is used to
  202.      * create this component's peer.
  203.      * Here it's used to get the length of the largest string in the list.
  204.      *
  205.      * @see java.awt.Container#removeNotify
  206.      */
  207.     public void addNotify()
  208.     {
  209.         super.addNotify();
  210.  
  211.         //Hook up listeners
  212.         if (focus == null)
  213.         {
  214.             focus = new Focus();
  215.             textFld.addFocusListener(focus);
  216.         }
  217.  
  218.         if (list.size() > 0)
  219.         {
  220.             int oldTextWidth = textWidth;
  221.  
  222.             for (Enumeration e = list.elements(); e.hasMoreElements();)
  223.             {
  224.                 textWidth = Math.max(textWidth, ((String)e.nextElement()).length());
  225.             }
  226.  
  227.             text = (String)list.elementAt(current);
  228.         }
  229.     }
  230.  
  231.     /**
  232.      * Tells this component that it is being removed from a container.
  233.      * This is a standard Java AWT method which gets called by the AWT when
  234.      * this component is removed from a container. Typically, it is used to
  235.      * destroy the peers of this component and all its subcomponents.
  236.      *
  237.      * It has been overridden here to unhook event listeners.
  238.      *
  239.      * @see #addNotify
  240.      */
  241.     public synchronized void removeNotify()
  242.     {
  243.         //Unhook listeners
  244.         if (focus != null)
  245.         {
  246.             textFld.removeFocusListener(focus);
  247.             focus = null;
  248.         }
  249.  
  250.         super.removeNotify();
  251.     }
  252.  
  253.  
  254.     /**
  255.      * Adds a string to the end of the list.
  256.      * @param s the string to be appended to the list
  257.      * @see #setListItems
  258.      */
  259.     public void addItem(String s)
  260.     {
  261.         int oldTextWidth = textWidth;
  262.  
  263.         list.addElement(s);
  264.         textWidth = Math.max(textWidth, s.length());
  265.     }
  266.  
  267.     /**
  268.      * Adds a listener for all property change events.
  269.      * @param listener the listener to add
  270.      * @see #removePropertyChangeListener
  271.      */
  272.     public synchronized void addPropertyChangeListener(PropertyChangeListener listener)
  273.     {
  274.         super.addPropertyChangeListener(listener);
  275.         changes.addPropertyChangeListener(listener);
  276.     }
  277.  
  278.     /**
  279.      * Removes a listener for all property change events.
  280.      * @param listener the listener to remove
  281.      * @see #addPropertyChangeListener
  282.      */
  283.     public synchronized void removePropertyChangeListener(PropertyChangeListener listener)
  284.     {
  285.         super.removePropertyChangeListener(listener);
  286.         changes.removePropertyChangeListener(listener);
  287.     }
  288.  
  289.     /**
  290.      * Adds a listener for all vetoable property change events.
  291.      * @param listener the listener to add
  292.      * @see #removeVetoableChangeListener
  293.      */
  294.     public synchronized void addVetoableChangeListener(VetoableChangeListener listener)
  295.     {
  296.         super.addVetoableChangeListener(listener);
  297.         vetos.addVetoableChangeListener(listener);
  298.     }
  299.  
  300.     /**
  301.      * Removes a listener for all vetoable property change events.
  302.      * @param listener the listener to remove
  303.      * @see #addVetoableChangeListener
  304.      */
  305.     public synchronized void removeVetoableChangeListener(VetoableChangeListener listener)
  306.     {
  307.         super.removeVetoableChangeListener(listener);
  308.         vetos.removeVetoableChangeListener(listener);
  309.     }
  310.  
  311.     /**
  312.      * Adds a listener for the max property changes.
  313.      * @param listener the listener to add.
  314.      * @see #removeMaxListener(java.beans.PropertyChangeListener)
  315.      */
  316.     public synchronized void addMaxListener(PropertyChangeListener listener)
  317.     {
  318.         changes.addPropertyChangeListener("max", listener);
  319.     }
  320.  
  321.     /**
  322.      * Removes a listener for the max property changes.
  323.      * @param listener the listener to remove.
  324.      * @see #addMaxListener(java.beans.PropertyChangeListener)
  325.      */
  326.     public synchronized void removeMaxListener(PropertyChangeListener listener)
  327.     {
  328.         changes.removePropertyChangeListener("max", listener);
  329.     }
  330.  
  331.     /**
  332.      * Adds a vetoable listener for the max property changes.
  333.      * @param listener the listener to add.
  334.      * @see #removeMaxListener(java.beans.VetoableChangeListener)
  335.      */
  336.     public synchronized void addMaxListener(VetoableChangeListener listener)
  337.     {
  338.         vetos.addVetoableChangeListener("max", listener);
  339.     }
  340.  
  341.     /**
  342.      * Removes a vetoable listener for the max property changes.
  343.      * @param listener the listener to remove.
  344.      * @see #addMaxListener(java.beans.VetoableChangeListener)
  345.      */
  346.     public synchronized void removeMaxListener(VetoableChangeListener listener)
  347.     {
  348.         vetos.removeVetoableChangeListener("max", listener);
  349.     }
  350.  
  351.     /**
  352.      * This is the Focus Event handling innerclass.
  353.      */
  354.     class Focus implements java.awt.event.FocusListener
  355.     {
  356.         /**
  357.          * Handles Focus Gained events
  358.          * @param e the FocusEvent
  359.          */
  360.         public void focusGained(FocusEvent e)
  361.         {
  362.             if(textFld.isEditable())
  363.             {
  364.                 isPossibleEdit = true;
  365.                 oldText = textFld.getText();
  366.                 textFld.setText(getCurrentText());
  367.             }
  368.         }
  369.  
  370.         /**
  371.          * Handles Focus Lost events
  372.          * @param e the FocusEvent
  373.          */
  374.         public void focusLost(FocusEvent e)
  375.         {
  376.             updateText(false);
  377.             isPossibleEdit = false;
  378.         }
  379.     }
  380.  
  381.     /**
  382.      * Gets the current contets and truncates the string appropriately.
  383.      * This assumes it is geting called from updateText
  384.      * @param contents the string to truncate as if it were to be placed in
  385.      * the textField
  386.      * @return The truncated string.  This may not be truncated if it was not needed.
  387.      */
  388.     protected String truncateContents(String contents)
  389.     {
  390.         if(added)
  391.         {
  392.             FontMetrics fm;
  393.             fm = getFontMetrics(textFld.getFont());
  394.             int        stringWidth            = fm.stringWidth(contents);
  395.  
  396.             if(stringWidth <= 0)
  397.                 return "";
  398.  
  399.             int        charWidth            = fm.stringWidth("W");
  400.             int        textFieldPadWidth    = textFld.getPreferredSize(1).width - charWidth;
  401.             int        maxTextWidth        = textFld.getSize().width - textFieldPadWidth;
  402.  
  403.             if(maxTextWidth < 0)
  404.                 maxTextWidth = 0;
  405.  
  406.             String testString = contents;
  407.             int stringIndex = testString.length();
  408.             while(stringIndex > 0 && fm.stringWidth(testString) > maxTextWidth - charWidth)
  409.             {
  410.                 testString = contents.substring(0, stringIndex);
  411.                 --stringIndex;
  412.             }
  413.             if(stringIndex != contents.length())
  414.             {
  415.                 testString += "...";
  416.                 while(stringIndex > 0 && fm.stringWidth(testString) > maxTextWidth - charWidth)
  417.                 {
  418.                     testString = contents.substring(0, stringIndex) + "...";
  419.                     --stringIndex;
  420.                 }
  421.             }
  422.  
  423.             return (testString);
  424.         }
  425.  
  426.         return ("");
  427.     }
  428.  
  429.     /**
  430.      * Increments the spinner's value and handles wrapping as needed.
  431.      * @see #scrollDown
  432.      * @see symantec.itools.awt.util.spinner.Spinner#increment
  433.      */
  434.     protected void scrollUp()
  435.     {
  436.         try
  437.         {
  438.             setCurrent(current + increment);
  439.         }
  440.         catch (PropertyVetoException exc)
  441.         {
  442.             if (isWrappable())
  443.             {
  444.                 try { setCurrent(min); } catch (PropertyVetoException exc1) {}
  445.             }
  446.             else
  447.             {
  448.                 try { setCurrent(internalMax); } catch (PropertyVetoException exc1) {}
  449.             }
  450.         }
  451.  
  452.         updateText(true);
  453.     }
  454.  
  455.     /**
  456.      * Decrements the spinner's value and handles wrapping as needed.
  457.      * @see #scrollUp
  458.      * @see symantec.itools.awt.util.spinner.Spinner#increment
  459.      */
  460.     protected void scrollDown()
  461.     {
  462.         try
  463.         {
  464.             setCurrent(current - increment);
  465.         }
  466.         catch (PropertyVetoException exc)
  467.         {
  468.             if (isWrappable())
  469.             {
  470.                 try { setCurrent(internalMax); } catch (PropertyVetoException exc1) {}
  471.             }
  472.             else
  473.             {
  474.                 try { setCurrent(min); } catch (PropertyVetoException exc1) {}
  475.             }
  476.         }
  477.  
  478.         updateText(false);
  479.     }
  480.  
  481.     protected void updateText(boolean force)
  482.     {
  483.         String        currentText            = getCurrentText();
  484.         String        truncContents        = truncateContents(currentText);
  485.         String        textFieldContents    = textFld.getText();
  486.         boolean    isListEmpty            = (list == null || list.size() <= 0);
  487.  
  488.         //If the text has changed, put the new text into the text field
  489.         if (!textFieldContents.equals(truncContents))
  490.         {
  491.             if(isPossibleEdit && !list.contains(textFieldContents) && !textFieldContents.equals(""))
  492.             {
  493.                 editAdding = true;
  494.                 addItem(textFieldContents);
  495.                 updateInternalMax();
  496.                 updateButtonStatus();
  497.                 truncContents    = truncateContents(getCurrentText());
  498.                 isListEmpty        = false;
  499.             }
  500.             else
  501.                 editAdding = false;
  502.  
  503.             if(!isListEmpty)
  504.                 textFld.setText(truncContents);
  505.         }
  506.  
  507.         if(isListEmpty)
  508.             textFld.setText("");
  509.     }
  510.  
  511.     /**
  512.      * Is the given value valid for the Current property .
  513.      * @param i the given value
  514.      * @return true if the given value is acceptable, false if not.
  515.      */
  516.     protected boolean isValidCurrentValue(int i)
  517.     {
  518.         return (i >= min && i <= internalMax);
  519.     }
  520.  
  521.     /**
  522.      * Is the given value valid for the Max property .
  523.      * @param i the given value
  524.      * @return true if the given value is acceptable, false if not.
  525.      */
  526.     protected boolean isValidMaxValue(int i)
  527.     {
  528.         return (i >= min && i >= 0);
  529.     }
  530.  
  531.     /**
  532.      * Is the given value valid for the Min property .
  533.      * @param i the given value
  534.      * @return true if the given value is acceptable, false if not.
  535.      */
  536.     protected boolean isValidMinValue(int i)
  537.     {
  538.         return (i <= internalMax && i >= 0);
  539.     }
  540.  
  541.     /**
  542.      * Keeps track of the maximum value the list is allowed to go to.
  543.      */
  544.     protected void updateInternalMax()
  545.     {
  546.         internalMax = 0;
  547.         int listSize = list.size();
  548.         if(max >= listSize)
  549.         {
  550.             if(listSize > 0)
  551.             {
  552.                 internalMax = listSize - 1;
  553.             }
  554.         }
  555.         else
  556.         {
  557.             internalMax = max;
  558.         }
  559.     }
  560.  
  561.     /**
  562.      * Handles enabling or disabling the spinner buttons as needed.
  563.      */
  564.     protected void updateButtonStatus()
  565.     {
  566.         if(buttons != null)
  567.         {
  568.             if(isWrappable())
  569.             {
  570.                 buttons.setUpButtonEnabled(true);
  571.                 buttons.setDownButtonEnabled(true);
  572.             }
  573.             else
  574.             {
  575.                 if(current == internalMax && current == min)
  576.                 {
  577.                     buttons.setUpButtonEnabled(false);
  578.                     buttons.setDownButtonEnabled(false);
  579.                 }
  580.                 else if(current == internalMax)
  581.                 {
  582.                     buttons.setUpButtonEnabled(false);
  583.                     buttons.setDownButtonEnabled(true);
  584.                 }
  585.                 else if(current == min)
  586.                 {
  587.                     buttons.setUpButtonEnabled(true);
  588.                     buttons.setDownButtonEnabled(false);
  589.                 }
  590.                 else
  591.                 {
  592.                     buttons.setUpButtonEnabled(true);
  593.                     buttons.setDownButtonEnabled(true);
  594.                 }
  595.             }
  596.         }
  597.     }
  598.  
  599.     /**
  600.      * The list of strings that get displayed in the spinner.
  601.      */
  602.     protected Vector list;
  603.  
  604.     /**
  605.      * Flag to keep track of allowance of dynamic resizing of the edit box as new values are entered.
  606.      */
  607.     protected boolean dynamicResizing;
  608.  
  609.     /**
  610.      * The maximum value the list is allowed to go to.
  611.      */
  612.     protected int internalMax;
  613.  
  614.     /**
  615.      * Text value at the time focus was gained.
  616.      */
  617.     protected String oldText = "";
  618.     /**
  619.      * Is possible to edit the component. True when it has the focus.
  620.      */
  621.     protected boolean isPossibleEdit = false;
  622.  
  623.     private boolean editAdding = false;
  624.  
  625.     private Focus focus = null;
  626.  
  627.     private symantec.itools.beans.VetoableChangeSupport vetos = new symantec.itools.beans.VetoableChangeSupport(this);
  628.     private symantec.itools.beans.PropertyChangeSupport changes = new symantec.itools.beans.PropertyChangeSupport(this);
  629. }
  630.